package com.geekydoo;

import java.util.logging.Logger;

import com.geekydoo.commands.AddCommand;
import com.geekydoo.commands.ClearCommand;
import com.geekydoo.commands.Command;
import com.geekydoo.commands.DeleteCommand;
import com.geekydoo.commands.DisplayCommand;
import com.geekydoo.commands.EditCommand;
import com.geekydoo.commands.ExitCommand;
import com.geekydoo.commands.HelpCommand;
import com.geekydoo.commands.OpenCommand;
import com.geekydoo.commands.SearchCommand;
import com.geekydoo.commands.UndoCommand;

/**
 * The purpose of this class is to parse user input and return the corresponding
 * command object type.
 * 
 * @author Wai Yau
 */
public final class CommandParser {

	private static Logger logger = Logger.getLogger("geekydoo");

	// The singleton reference to an instance of this class
	private static CommandParser commandParser;

	// The strings corresponding to valid command types
	private static final String CMD_ADD = "((?i)add|(?i)new|(?i)create)";
	private static final String CMD_DISPLAY = "((?i)display)|((?i)show)";
	private static final String CMD_DELETE = "((?i)delete)|((?i)remove|(?i)erase)";
	private static final String CMD_EDIT = "((?i)edit)|((?i)modify)|((?i)change)";
	private static final String CMD_CLEAR = "(?i)clear";
	private static final String CMD_SEARCH = "((?i)search)|((?i)find|(?i)seek)";
	private static final String CMD_OPEN = "(?i)open";
	private static final String CMD_UNDO = "(?i)undo";
	private static final String CMD_EXIT = "((?i)exit)|((?i)quit)|((?i)stop|(?i)bye)";
	private static final String CMD_HELP = "((?i)help|(?i)instructions|(?i)guide)";

	// Error messages printed by the CommandParser
	private static final String ERROR_UNEXPECTED = "Unexpected error in the program!";

	// Enumeration of all possible command types
	public enum CommandTypes {
		ADD, DISPLAY, DELETE, EDIT, CLEAR, SEARCH, OPEN, UNDO, EXIT, HELP, INVALID
	};

	private CommandParser() {

	}

	/**
	 * This method provides the CommandParser to be used.
	 * 
	 * @return the CommandParser to be used
	 */
	public static CommandParser getCommandParser() {
		if (commandParser == null) {
			commandParser = new CommandParser();
		}
		assert (commandParser != null);
		logger.info("Returning the CommandParser");
		return commandParser;
	}

	/**
	 * This method returns the appropriate command object based on user input.
	 * 
	 * @param commandString User input string
	 * @return the command object to be executed
	 */
	public Command parseCommand(String commandString) {
		CommandTypes commandType = convertStringToCommandType(commandString);
		Command command = null;
		switch (commandType) {
			case ADD:
				command = new AddCommand();
				logger.info("Returning an AddCommand");
				break;
			case DISPLAY:
				command = new DisplayCommand();
				logger.info("Returning a DisplayCommand");
				break;
			case DELETE:
				command = new DeleteCommand();
				logger.info("Returning a DeleteCommand");
				break;
			case CLEAR:
				command = new ClearCommand();
				logger.info("Returning a ClearCommand");
				break;
			case EDIT:
				command = new EditCommand();
				logger.info("Returning an EditCommand");
				break;
			case SEARCH:
				command = new SearchCommand();
				logger.info("Returning a SearchCommand");
				break;
			case OPEN:
				command = new OpenCommand();
				logger.info("Returning an OpenCommand");
				break;
			case UNDO:
				command = new UndoCommand();
				logger.info("Returning an UndoCommand");
				break;
			case EXIT:
				command = new ExitCommand();
				logger.info("Returning an ExitCommand");
				break;
			case HELP:
				command = new HelpCommand();
				logger.info("Returning a HelpCommand");
				break;
			case INVALID:
				logger.info("Returning null, command \"" + commandString + "\" couldn't be parsed");
				break;
			default:
				logger.severe("Execution reached a point it shouldn't reach, check logic in CommandParser!");
				assert (false);
				throw new Error(ERROR_UNEXPECTED);
		}
		return command;
	}

	/**
	 * This method converts the commandString argument to the appropriate
	 * command type.
	 * 
	 * @param commandString User input string
	 * @return the appropriate CommandTypes enumeration value
	 */
	public CommandTypes convertStringToCommandType(String commandString) {
		assert (commandString != null);

		if (commandString.matches(CMD_ADD)) {
			return CommandTypes.ADD;
		}
		else if (commandString.matches(CMD_DISPLAY)) {
			return CommandTypes.DISPLAY;
		}
		else if (commandString.matches(CMD_DELETE)) {
			return CommandTypes.DELETE;
		}
		else if (commandString.matches(CMD_EDIT)) {
			return CommandTypes.EDIT;
		}
		else if (commandString.matches(CMD_CLEAR)) {
			return CommandTypes.CLEAR;
		}
		else if (commandString.matches(CMD_SEARCH)) {
			return CommandTypes.SEARCH;
		}
		else if (commandString.matches(CMD_OPEN)) {
			return CommandTypes.OPEN;
		}
		else if (commandString.matches(CMD_UNDO)) {
			return CommandTypes.UNDO;
		}
		else if (commandString.matches(CMD_EXIT)) {
			return CommandTypes.EXIT;
		}
		else if (commandString.matches(CMD_HELP)) {
			return CommandTypes.HELP;
		}
		else {
			return CommandTypes.INVALID;
		}
	}

}
